#include "NeoCalendarPrivate.h"
#include "NeoCalendar.h"
#include "NeoPushButton.h"
#include "NeoScrollBar.h"
#include "common/NeoBaseListView.h"
#include "common/NeoCalendarDelegate.h"
#include "common/NeoCalendarHeaderDelegate.h"
#include "common/NeoCalendarHeaderModel.h"
#include "common/NeoCalendarMask.h"
#include "common/NeoCalendarModel.h"
#include <QApplication>
#include <QPropertyAnimation>
#include <QTimer>
#include <QVBoxLayout>

NeoCalendarPrivate::NeoCalendarPrivate(QObject *parent)
    : QObject(parent),
      pViewOpacity(0),
      pTitleButton(nullptr),
      pPrevButton(nullptr),
      pNextButton(nullptr),
      pHeaderView(nullptr),
      pCalendarView(nullptr),
      pCalendarMask(nullptr),
      pHeaderModel(new NeoCalendarHeaderModel(this)),
      pHeaderDelegate(new NeoCalendarHeaderDelegate(this)),
      pCalendarModel(new NeoCalendarModel(this)),
      pCalendarDelegate(new NeoCalendarDelegate(this)),
      pDisplayMode(NeoCalendarType::DayView),
      pCenterIndex(QModelIndex()),
      pDisplayModeAnimation(new QPropertyAnimation(this, "pViewOpacity", this))
{
    pCalendarModel->setDisplayMode(NeoCalendarType::DayView);
    pCalendarDelegate->setDisplayMode(NeoCalendarType::DayView);
    pHeaderDelegate->setDisplayMode(NeoCalendarType::DayView);
    pDisplayModeAnimation->setDuration(300);
    pDisplayModeAnimation->setStartValue(0);
    pDisplayModeAnimation->setEndValue(1);
    pDisplayModeAnimation->setEasingCurve(QEasingCurve::InOutCubic);
}

NeoCalendarPrivate::~NeoCalendarPrivate() = default;

void NeoCalendarPrivate::scrollToDate(const QDate &date, bool isAnimation)
{
    switch (pDisplayMode)
    {
    case NeoCalendarType::DisplayMode::DayView:
    {
        QDate showDate = QDate(date.year(), date.month(), 1);
        QModelIndex index = pCalendarModel->getIndexFromDate(showDate);
        int pos = index.row() / 7 * pCalendarView->sizeHintForRow(0);
        NeoScrollBar *scrollbar = qobject_cast<NeoScrollBar *>(pCalendarView->verticalScrollBar());
        isAnimation ? scrollbar->setAnimationValue(pos) : scrollbar->setValue(pos);
        break;
    }
    case NeoCalendarType::DisplayMode::MonthView:
    {
        QDate showDate = QDate(date.year(), 1, 1);
        QModelIndex index = pCalendarModel->getIndexFromDate(showDate);
        int pos = index.row() / 4 * pCalendarView->sizeHintForRow(0);
        NeoScrollBar *scrollbar = qobject_cast<NeoScrollBar *>(pCalendarView->verticalScrollBar());
        isAnimation ? scrollbar->setAnimationValue(pos) : scrollbar->setValue(pos);
        break;
    }
    case NeoCalendarType::DisplayMode::YearView:
    {
        QDate showDate = QDate(date.year() / 16 * 16, 1, 1);
        QModelIndex index = pCalendarModel->getIndexFromDate(showDate);
        int pos = index.row() / 4 * pCalendarView->sizeHintForRow(0);
        NeoScrollBar *scrollbar = qobject_cast<NeoScrollBar *>(pCalendarView->verticalScrollBar());
        isAnimation ? scrollbar->setAnimationValue(pos) : scrollbar->setValue(pos);
        break;
    }
    default:
        break;
    }
}

void NeoCalendarPrivate::changeDisplayMode(NeoCalendarType::DisplayMode mode)
{
    pDisplayMode = mode;
    pCalendarModel->setDisplayMode(pDisplayMode);
    pCalendarDelegate->setDisplayMode(pDisplayMode);
    int itemWidth = pCalendarView->sizeHintForColumn(0);
    int itemHeight = pCalendarView->sizeHintForRow(0);
    QSize viewportSize = pCalendarView->viewport()->size();
    switch (pDisplayMode)
    {
    case NeoCalendarType::DisplayMode::DayView:
    {
        pCalendarView->setOffsetX((viewportSize.width() - itemWidth * 7) / 2);
        break;
    }
    case NeoCalendarType::DisplayMode::MonthView:
    {
        pCalendarView->setOffsetX((viewportSize.width() - itemWidth * 4) / 2);
        break;
    }
    case NeoCalendarType::DisplayMode::YearView:
    {
        pCalendarView->setOffsetX((viewportSize.width() - itemWidth * 4) / 2);
        break;
    }
    default:
        break;
    }
    updateCenterIndex();
}

void NeoCalendarPrivate::updateCenterIndex()
{
    int itemWidth = pCalendarView->sizeHintForColumn(0);
    int itemHeight = pCalendarView->sizeHintForRow(0);
    switch (pDisplayMode)
    {
    case NeoCalendarType::DayView:
    {
        pCenterIndex = pCalendarView->indexAt(QPoint(itemWidth * 3 + pCalendarView->getOffsetX(),
                                                     itemHeight * 2 + pCalendarView->getOffsetY()));

        QDate date = pCenterIndex.data().toDate();
        pTitleButton->setText(date.toString("yyyy-MM"));
        break;
    }
    case NeoCalendarType::MonthView:
    {
        pCenterIndex = pCalendarView->indexAt(QPoint(itemWidth * 2 + pCalendarView->getOffsetX(),
                                                     itemHeight * 1 + pCalendarView->getOffsetY()));
        QDate date = pCenterIndex.data().toDate();
        pTitleButton->setText(date.toString("yyyy"));
        break;
    }
    case NeoCalendarType::YearView:
    {
        pCenterIndex = pCalendarView->indexAt(QPoint(itemWidth * 2 + pCalendarView->getOffsetX(),
                                                     itemHeight * 1 + pCalendarView->getOffsetY()));
        QDate date = pCenterIndex.data().toDate();
        pTitleButton->setText(QString::number(date.year() / 16 * 16) + "-" +
                              QString::number(date.year() / 16 * 16 + 15));

        break;
    }
    default:
        break;
    }
}

void NeoCalendarPrivate::onTitleButtonClicked()
{
    NEO_PRIVATE_POINT(NeoCalendar)
    QDate date = pCenterIndex.data().toDate();
    switch (pDisplayMode)
    {
    case NeoCalendarType::DayView:
    {
        QRect rect = pHeaderView->geometry();
        rect.setBottomRight(pCalendarView->geometry().bottomRight());
        QPixmap before = q->grab(rect);
        pHeaderView->hide();
        changeDisplayMode(NeoCalendarType::MonthView);
        scrollToDate(date, false);
        QApplication::processEvents();
        QPixmap after = pCalendarView->grab();
        pCalendarDelegate->setTransparent(true);
        pCalendarMask->setStartPixmap(before);
        pCalendarMask->setEndPixmap(after);
        pCalendarMask->setGeometry(pCalendarView->geometry());
        pCalendarMask->setAnimationFinishedCallback([this]()
                                                    { pCalendarDelegate->setTransparent(false); });
        pCalendarMask->raise();
        pCalendarMask->show();
        pCalendarMask->startAnimation(false);
        break;
    }
    case NeoCalendarType::MonthView:
    {
        QPixmap before = pCalendarView->grab();
        changeDisplayMode(NeoCalendarType::YearView);
        scrollToDate(date, false);
        QPixmap after = pCalendarView->grab();
        pCalendarDelegate->setTransparent(true);
        pCalendarMask->setStartPixmap(before);
        pCalendarMask->setEndPixmap(after);
        pCalendarMask->setGeometry(pCalendarView->geometry());
        pCalendarMask->setAnimationFinishedCallback([this]()
                                                    { pCalendarDelegate->setTransparent(false); });
        pCalendarMask->raise();
        pCalendarMask->show();
        pCalendarMask->startAnimation(false);
        break;
    }
    case NeoCalendarType::YearView:
    {
        break;
    }
    default:
        break;
    }
}

void NeoCalendarPrivate::onPrevButtonClicked()
{
    NeoScrollBar *scrollbar = qobject_cast<NeoScrollBar *>(pCalendarView->verticalScrollBar());
    scrollbar->setScrollSpeed(3);
    QDate date = pCenterIndex.data().toDate();
    switch (pDisplayMode)
    {
    case NeoCalendarType::DayView:
    {
        scrollToDate(date.addMonths(-1));
        break;
    }
    case NeoCalendarType::MonthView:
    {
        scrollToDate(date.addYears(-1));
        break;
    }
    case NeoCalendarType::YearView:
    {
        scrollToDate(date.addYears(-16));
        break;
    }
    default:
        break;
    }
    QTimer::singleShot(300, [scrollbar]()
                       { scrollbar->setScrollSpeed(1); });
}

void NeoCalendarPrivate::onNextButtonClicked()
{
    NeoScrollBar *scrollbar = qobject_cast<NeoScrollBar *>(pCalendarView->verticalScrollBar());
    scrollbar->setScrollSpeed(3);
    QDate date = pCenterIndex.data().toDate();
    switch (pDisplayMode)
    {
    case NeoCalendarType::DayView:
    {
        scrollToDate(date.addMonths(1));
        break;
    }
    case NeoCalendarType::MonthView:
    {
        scrollToDate(date.addYears(1));
        break;
    }
    case NeoCalendarType::YearView:
    {
        scrollToDate(date.addYears(16));
        break;
    }
    default:
        break;
    }
    QTimer::singleShot(300, [scrollbar]()
                       { scrollbar->setScrollSpeed(1); });
}

void NeoCalendarPrivate::onDateIndexClicked(const QModelIndex &index)
{
    NEO_PRIVATE_POINT(NeoCalendar)
    QDate date = index.data().toDate();
    switch (pDisplayMode)
    {
    case NeoCalendarType::DayView:
    {
        break;
    }
    case NeoCalendarType::MonthView:
    {
        QPixmap before = pCalendarView->grab();
        pHeaderView->show();
        QRect rect = pHeaderView->geometry();
        rect.setBottomRight(pCalendarView->geometry().bottomRight());
        changeDisplayMode(NeoCalendarType::DayView);
        scrollToDate(date, false);
        QPixmap after = q->grab(rect);
        pCalendarDelegate->setTransparent(true);
        pHeaderDelegate->setTransparent(true);
        pCalendarMask->setStartPixmap(before);
        pCalendarMask->setEndPixmap(after);
        pCalendarMask->setGeometry(rect);
        pCalendarMask->setAnimationFinishedCallback([this]()
                                                    { pCalendarDelegate->setTransparent(false);
                                                        pHeaderDelegate->setTransparent(false); });
        pCalendarMask->raise();
        pCalendarMask->show();
        pCalendarMask->startAnimation(true);
        break;
    }
    case NeoCalendarType::YearView:
    {
        QPixmap before = pCalendarView->grab();
        changeDisplayMode(NeoCalendarType::MonthView);
        scrollToDate(date, false);
        QPixmap after = pCalendarView->grab();
        pCalendarDelegate->setTransparent(true);
        pCalendarMask->setStartPixmap(before);
        pCalendarMask->setEndPixmap(after);
        pCalendarMask->setGeometry(pCalendarView->geometry());
        pCalendarMask->setAnimationFinishedCallback([this]()
                                                    { pCalendarDelegate->setTransparent(false); });
        pCalendarMask->raise();
        pCalendarMask->show();
        pCalendarMask->startAnimation(true);
        break;
    }
    }
}